package client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.UnknownHostException;
import util.*;


public class Client {

	public static final int DEFAULT_PORT = 6125;

	public static final int STATE_PRE_CONNECT = 0; // Nothing have happened yet
	public static final int STATE_CONNECTED = 1;   // Socket is open. Client should send username.
	public static final int STATE_LOGGING_IN = 2;  // Username sent. Waiting for confirmation from server.
	public static final int STATE_LOGGED_IN = 3;   // User is logged in. Game can now commence. 

	private static int state = STATE_PRE_CONNECT;
	
	private static ConsoleInputHandler consoleInputHandler = new PrivateConsoleInputHandler();
	
	private static ConsoleUI console = new ConsoleUI("Client",consoleInputHandler);
	private static ClientSocketThread socketThread;
	
	
	/**
	 * Does nothing.
	 * @param args is not parsed.
	 */
	public static void main(String[] args) {
		// intentionally left blank. GUI takes care of user-input-thread.
	}
	
	/**
	 * Returns the state of the client.
	 * @return the state
	 */
	public static int getState() {
		return state;
	}
	
	/**
	 * Print to the gui console.
	 * @param string the message to print.
	 */
	public static synchronized void consolePrint(String string) {
		if (string != null)
			console.println(string);
	}
	
	/**
	 * Process a line from the user.
	 * @param str the user input.
	 */
	public static synchronized void processUserInput(String str) {
		
		if (socketThread != null && socketThread.isClosed()) {
			state = STATE_PRE_CONNECT;
			console.println("Connection have been reset.");
		}
		
		if (str != null && !str.trim().equals("")) {
			String[] tokens = str.split(" ");
			if (tokens.length >= 1){
				if (str.equalsIgnoreCase("exit")) {
					socketThread.send(str);
					state = STATE_PRE_CONNECT;
					socketThread.close();
				} else {
					switch (state) {
						case STATE_PRE_CONNECT:
							
							if ( tokens[0].equalsIgnoreCase("connect") ) {
								
								int port = DEFAULT_PORT;
								
								if ( tokens.length > 2 ){
									try {
										port = Integer.parseInt(tokens[2]);
									} catch (NumberFormatException e){
										console.println("There was an error reading the number you typed. Correct usage of connect is:\n"+
												"connect ip-address port \n Connecting to default port: " + DEFAULT_PORT);
									}
								}
								
								if (connect(tokens[1],port)) {
									console.println("You are now connected to "+ tokens[1] + ":" + port);
									state = STATE_CONNECTED;
								} else {
									console.println("Connection failed.");
								}
							} else {
								console.println("Correct syntax is:\nconnect server [port]");
							}
							break;
							
						case STATE_CONNECTED:
							socketThread.send(tokens[0]); // Print the first word. The username should not have spaces.
							state = STATE_LOGGING_IN;
							break;
							
						case STATE_LOGGING_IN:
							console.println("Have patience.");
							break;
							
						case STATE_LOGGED_IN:
							if (str.length() == 1) {
								socketThread.send(str);
							} else 
								Client.consolePrint("Type a valid answer, i.e., only one character.");
							
							break;
					
					}
				}
			} 
		}
	}
	
	/**
	 * Process a message from the server according to the protocol
	 * @param str message from the server
	 */
	public static synchronized void processServerInput(String str) {
		if (str != null) {
			switch (state) {
				case STATE_PRE_CONNECT:
					/*
					 * There should be no input when the server is not connected
					 */
					break;
					
				case STATE_CONNECTED:
					// Print everything. This contains info to the client and a list of usernames
					console.println(str);
					break;
					
				case STATE_LOGGING_IN:
					// Confirm that the server acknowledges the username
					String[] tokens = str.split(" ", 2);
					if (tokens.length == 2 && tokens[0].equalsIgnoreCase("welcome")){
						console.println("You are now connected as "+ tokens[1]);
						state = STATE_LOGGED_IN;
					} else {
						state = STATE_CONNECTED;
						console.println("Username already taken. Try a new.");
					}
					break;
					
				case STATE_LOGGED_IN:
					// Print everything. This contains game-info from server.
					console.println(str);
					break;
			}
		}
		if (socketThread != null && socketThread.isClosed()) {
			state = STATE_PRE_CONNECT;
			console.println("Connection have been reset.");
		}
	}
	
	/**
	 * Attempt to connect to the given ip/hostname and port
	 * @param host ip or hostname of the server
	 * @param port the port to connect to
	 * @return true if connection was a success, false otherwise
	 */
	private static boolean connect(String host, int port) {
		boolean r = false;
		try {
			socketThread = new ClientSocketThread(new Socket(host,port));
			socketThread.start();
			r = true;
			
		} catch (UnsupportedEncodingException e) {
			// OS doens't support UTF-8? This machine shouldn't even be running.
			e.printStackTrace();
			System.out.println("Server or client doesn't support UTF-8.");
			socketThread.close();
			
		} catch (UnknownHostException e) {
			console.println("Server could not be found.");
			
		} catch (IOException e) {
			console.println("An error occured.");
			e.printStackTrace();
			
		}
		return r;
	}

	/**
	 * Drops current connection and returns to initial state.
	 */
	public static void resetConnection(){
		if (socketThread != null && !socketThread.isClosed())
			socketThread.close();
		
		state = STATE_PRE_CONNECT;
		console.println("Connection reset. To connect, type \"connect server [port]\"");
	}

	/**
	 * Override the ConsoleInputHandler to redirect the input to processUsierInput()
	 * @author lasse
	 *
	 */
	private static class PrivateConsoleInputHandler extends ConsoleInputHandler {
		
		public void processConsoleInput(String str) {
			processUserInput(str);
		}
	}
}
